#region Header File
//
// ImaDecoder.cs - IMA audio decoder
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

namespace Dsp
{
    /// <summary>
    /// IMA audio codec (Decoder)
    /// </summary>
    public class ImaDecoder
    {
        sbyte index; // index for step table          __state
        int   pred;  // predicted signal (acumulated) __state

        static sbyte[] indexTable = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 };
        static int[]   deltaTable = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 
            31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 
            173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 
            724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 
            2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 
            9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 
            27086, 29794, 32767};

        #region Constructors
        public ImaDecoder()
        {
            // Set initial state
            index = 0;
            pred  = 0;
        }
        #endregion

        /// <summary>
        /// The value is the Least Significan Nibble (4 bits), the upper 4 bits are ignored.
        /// </summary>
        /// <param name="ibuf"></param>
        /// <returns></returns>
        public double Decode(byte code)
        {
            int    diff; // de-quantized residue
            int    step; // current quantizer step
            double smpl; // audio sample FP

            code &= 0x0F;

            // Reconstruct the residue
            step = deltaTable[index];
            diff = 0;

            if ((code & 4) != 0) diff += step;
            if ((code & 2) != 0) diff += step >> 1;
            if ((code & 1) != 0) diff += step >> 2;
            if ((code & 8) != 0) diff = -diff;

            // Genrerate de predicted sample using the reconstructed residue
            pred += diff;
            if (pred > +32767) pred = +32767;
            if (pred < -32768) pred = -32768;

            smpl = pred / 32768.0;

            // Update the index
            index += indexTable[code];
            if (index < 00) index = 00;
            if (index > 88) index = 88;

            return smpl;
        }


        /// <summary>
        /// Decodes two IMA samples per byte, i.e, packed, the first sample is in the 4 least 
        /// significant bits and the second is in the 4 most significant bits
        /// </summary>
        /// <param name="ibuf">Compressed data array</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of elements that must be processed</param>
        /// <returns>Uncompressed audio samples, in double format. The array has count * 2 elements</returns>
        public double[] DecodeTwo(byte[] ibuf, int offset, int count)
        {
            double[] obuf = new double[count * 2];
            byte     s0;
            byte     s1;

            for (int i = 0; i < count; i++)
            {
                s0 = (byte)((ibuf[offset + i] & 0x0F)     );
                s1 = (byte)((ibuf[offset + i] & 0xF0) >> 4);

                obuf[2*i+0] = Decode(s0);
                obuf[2*i+1] = Decode(s1);
            }

            return obuf;
        }

        /// <summary>
        /// Decodes one IMA samples per byte, i.e, not packed
        /// </summary>
        /// <param name="ibuf">Compressed data array</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of elements that must be processed</param>
        /// <returns>Uncompressed audio samples, in double format. The array has count * 1 elements</returns>
        public double[] DecodeOne(byte[] ibuf, int offset, int count)
        {
            double[] obuf = new double[count];

            for (int i = 0; i < count; i++)
            {
                obuf[i] = Decode(ibuf[offset + i]);
            }

            return obuf;
        }

        /// <summary>
        /// Set the internal state
        /// </summary>
        /// <param name="pred">Predicted sample</param>
        /// <param name="index">Index in the Index-ROM</param>
        public void SetState(int pred, sbyte index)
        {
            this.index = index;
            this.pred  = pred;
        }



        /// <summary>
        /// Get the internal state
        /// </summary>
        /// <param name="pred">Predicted sample</param>
        /// <param name="index">Index in the Index-ROM</param>
        public void GetState(out int pred, out sbyte index)
        {
            index = this.index;
            pred  = this.pred;
        }

    }
}
